#pragma once

#include "Utils.h"
#include "Texture.h"
#include "Material.h"
#include <qpixmap.h>
#include <qpainter.h>
#include <qprogressbar.h>

#define matrix QTransform

class Camera
{
public:
	int viewWidth, viewHeight;
	float rh, ratio, rw;
	vec org, target, up;
	float fov;
	vec dir, rdir, sup;

	Camera(int viewWidth, int viewHeight, float fov = 50, vec org = vec(0, 0, 0), vec target = vec(5, 0, 0), vec up = vec(0, 0, 1)) :
		viewWidth(viewWidth), viewHeight(viewHeight), org(org), target(target), fov(fov), up(up)
	{
		dir = target - org;
		rdir = vec::crossProduct(dir, up).normalized();
		sup = vec::crossProduct(rdir, dir).normalized();
		ratio = 1.0 * viewWidth / viewHeight;
		rh = tanf(fov / 2 * DEG2RAD);
		rw = rh * ratio;
	}
	void update()
	{
		dir = target - org;
		rdir = vec::crossProduct(dir, up).normalized();
		sup = vec::crossProduct(rdir, dir).normalized();
		ratio = 1.0 * viewWidth / viewHeight;
		rh = tanf(fov / 2 * DEG2RAD);
		rw = rh * ratio;
	}
	Ray camRay(float ix, float iy)
	{
		vec tarPos = org + dir.normalized();
		float ox = ix / viewWidth * 2 - 1;
		float oy = iy / viewHeight * 2 - 1;
		vec tar = tarPos - sup * oy * rh + rdir * ox * rw;
		return Ray(org, tar - org);
	}
};


enum class objType {
	NUL,
	Tri,
	Sph
};

class obj
{	
public:
	virtual bool hit(hitInfo& hi, const Ray& ray)const = 0;
	objType type = objType::NUL;
	material* mat;
	QString name;
};

class Sphere : public obj
{
public:
	vec cen;
	float rad;

	Sphere(QString name, vec cen, float rad, material* mat = defaultMat) :
		cen(cen), rad(rad) {
		this->mat = mat;
		this->name = name;
		type = objType::Sph;
	}

	bool hit(hitInfo& hi, const Ray& ray)const {
		vec oc = ray.org - cen;
		float b = 2 * vec::dotProduct(oc, ray.dir);
		float c = vec::dotProduct(oc, oc) - rad * rad;
		float delta = b * b - 4 * c;

		if (delta < 0) return false;

		float t = (-b - sqrtf(delta)) / 2;
		if (oc.length() < rad) t = (-b + sqrtf(delta)) / 2;
		if (t < 0) return false;

		hi.t = t;
		vec pos = ray.rich(t);
		hi.pos = pos;
		hi.normal = (pos - cen).normalized();
		hi.himat = mat;
		return true;
	}
};

class Triangle : public obj
{
public:
	vec p1, p2, p3;
	Triangle(QString name, vec p1, vec p2, vec p3, material* mat = defaultMat) :
		p1(p1), p2(p2), p3(p3) {
		this->mat = mat;
		this->name = name;
		type = objType::Tri;
	}
	bool hit(hitInfo& hi, const Ray& ray)const
	{
		vec e1 = p1 - p2;
		vec e2 = p1 - p3;
		vec s = p1 - ray.org;
		float a = matrix(s.x(), e1.x(), e2.x(), s.y(), e1.y(), e2.y(), s.z(), e1.z(), e2.z()).det();
		float b = matrix(ray.dir.x(), s.x(), e2.x(), ray.dir.y(), s.y(), e2.y(), ray.dir.z(), s.z(), e2.z()).det();
		float c = matrix(ray.dir.x(), e1.x(), s.x(), ray.dir.y(), e1.y(), s.y(), ray.dir.z(), e1.z(), s.z()).det();
		float d = matrix(ray.dir.x(), e1.x(), e2.x(), ray.dir.y(), e1.y(), e2.y(), ray.dir.z(), e1.z(), e2.z()).det();
		if (a / d < 0 || b / d < 0 || c / d < 0 || b / d + c / d > 1) return false;

		hi.t = a / d;
		vec pos = ray.rich(a / d);
		hi.pos = pos;
		vec l1 = p2 - p1;
		vec l2 = p3 - p2;
		hi.normal = vec::crossProduct(l1, l2).normalized();
		hi.himat = mat;
		return true;
	}
};

class obj_list : public obj
{
public:
	QList<obj*> list;
	obj_list(QList<obj*> list) :list(list) {}
	bool hit(hitInfo& hi, const Ray& ray)const
	{
		if (list.size() < 1) return false;
		bool fb = list[0]->hit(hi, ray);

		if (list.size() <= 1) return fb;
		for (int i = 0; i < list.size() - 1; i++) {
			hitInfo nhi(defaultMat);
			bool nb = list[i + 1]->hit(nhi, ray);
			if (!nb)continue;
			if (nhi.t < hi.t)
			{
				hi = nhi;
				fb = nb;
			}

		}
		return fb;
	}
};

class World
{
public:
	Camera cam = Camera(RESOLUTION,RESOLUTION);
	QList<obj*> objlist;
};

void initDefaultVar();

vec shading(const obj* scenes, const Ray& ray, const int& rayDepth, bool isfst = true);
QPixmap rend(World world, int sampleTimes, int rayDepth, QProgressBar* pb = NULL);

extern World mainWorld;